use std::collections::HashMap;

use crate::{
    document::{ApiDocument, ApiLayer},
    module::ApiModule,
    operation::ApiOperation,
    request::ApiRequest,
    response::ApiResponse,
    ty::{ApiTypeId, ApiTypeInfo},
};

pub type GetApiLayerFn = fn() -> ApiLayer;
pub type GetApiModuleFn = fn() -> ApiModule;
pub type GetApiOperationFn = fn(&mut HashMap<ApiTypeId, ApiTypeInfo>) -> ApiOperation;

pub struct ApiLayerSubmitItem {
    pub get_layer_fn: GetApiLayerFn,
}
pub struct ApiModuleSubmitItem {
    pub get_module_fn: GetApiModuleFn,
}
pub struct ApiOperationSubmitItem {
    pub get_operation_fn: GetApiOperationFn,
}

inventory::collect!(ApiLayerSubmitItem);
inventory::collect!(ApiModuleSubmitItem);
inventory::collect!(ApiOperationSubmitItem);

pub trait ApiRequestExtractor<DefinedType> {
    fn extract(_types: &mut HashMap<ApiTypeId, ApiTypeInfo>, _api_request: &mut ApiRequest) {}
}

impl<T> ApiRequestExtractor<()> for T {}

macro_rules! impl_tuple_request_extractor {
    ($($ident: ident,)*) => {
        impl<$($ident,)* T> ApiRequestExtractor<($($ident,)*)> for T
        where
            $(T: ApiRequestExtractor<$ident>,)*
        {
            fn extract(
                types: &mut HashMap<ApiTypeId, ApiTypeInfo>,
                api_request: &mut ApiRequest,
            ) {
                $(<T as ApiRequestExtractor<$ident>>::extract(types, api_request);)*
            }
        }
    };
}
impl_tuple_request_extractor!(A1,);
impl_tuple_request_extractor!(A1, A2,);
impl_tuple_request_extractor!(A1, A2, A3,);
impl_tuple_request_extractor!(A1, A2, A3, A4,);
impl_tuple_request_extractor!(A1, A2, A3, A4, A5,);
impl_tuple_request_extractor!(A1, A2, A3, A4, A5, A6,);
impl_tuple_request_extractor!(A1, A2, A3, A4, A5, A6, A7,);
impl_tuple_request_extractor!(A1, A2, A3, A4, A5, A6, A7, A8,);
impl_tuple_request_extractor!(A1, A2, A3, A4, A5, A6, A7, A8, A9,);
impl_tuple_request_extractor!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10,);
impl_tuple_request_extractor!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11,);
impl_tuple_request_extractor!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12,);
impl_tuple_request_extractor!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13,);
impl_tuple_request_extractor!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14,);
impl_tuple_request_extractor!(
    A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15,
);
impl_tuple_request_extractor!(
    A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16,
);

pub trait ApiResponseExtractor<DefinedType> {
    fn extract(_types: &mut HashMap<ApiTypeId, ApiTypeInfo>, _api_response: &mut ApiResponse) {}
}

pub struct ApiDocumentCollector;

impl ApiDocumentCollector {
    pub fn document() -> ApiDocument {
        let mut types = HashMap::new();
        let mut modules = vec![];

        for module in inventory::iter::<ApiModuleSubmitItem> {
            let module = (module.get_module_fn)();
            modules.push(module);
        }
        let mut operations = vec![];
        for operation in inventory::iter::<ApiOperationSubmitItem> {
            let mut tmp_types = HashMap::new();
            let operation = (operation.get_operation_fn)(&mut tmp_types);
            types.extend(tmp_types);
            operations.push(operation);
        }

        let mut layers = vec![];
        for layer in inventory::iter::<ApiLayerSubmitItem> {
            let layer = (layer.get_layer_fn)();
            layers.push(layer);
        }
        ApiDocument {
            modules,
            operations,
            types,
            layers,
        }
    }
}
